// Renderer.cpp
//
#include "Renderer2.h"

#include "OpenGL/OpenGL.h"
#include "ClearState.h"

using namespace OpenGL;

static void enable( GLenum cap, bool enabled )
{
	if( enabled ) glEnable(cap);
	else          glDisable(cap);
}

// ------------------------------------------------------------------------------------------------
Renderer::Renderer()
{}

// ------------------------------------------------------------------------------------------------
Renderer::~Renderer()
{}

// ------------------------------------------------------------------------------------------------
void Renderer::applyRenderState( const RenderState &rs )
{
	applyPrimitiveRestart( rs.primitiveRestart );
	applyFaceCulling( rs.faceCulling );
	applyProgramPointSize( rs.programPointSize );
	applyRasterizationMode( rs.rasterizationMode );
	applyScissorTest( rs.scissorTest );
	applyStencilTest( rs.stencilTest );
	applyDepthTest( rs.depthTest );
	applyDepthRange( rs.depthRange );
	applyBlending( rs.blending );
	applyColorMask( rs.colorMask );
	applyDepthMask( rs.depthMask );
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyPrimitiveRestart( const PrimitiveRestart &pr )
{
	//if( rs.primitiveRestart.Enabled != primitiveRestart.Enabled)
	//{
	//	Enable(EnableCap.PrimitiveRestart, primitiveRestart.Enabled);
	//	_renderState.PrimitiveRestart.Enabled = primitiveRestart.Enabled;
	//}

	//if (primitiveRestart.Enabled)
	//{
	//	if (_renderState.PrimitiveRestart.Index != primitiveRestart.Index)
	//	{
	//		GL.PrimitiveRestartIndex(primitiveRestart.Index);
	//		_renderState.PrimitiveRestart.Index = primitiveRestart.Index;
	//	}
	//}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyFaceCulling( const FaceCulling &fc )
{
	if( _renderState.faceCulling.enabled != fc.enabled )
	{
		enable( GL_CULL_FACE, fc.enabled);
		_renderState.faceCulling.enabled =  fc.enabled;
	}

	if( fc.enabled )
	{
		if( _renderState.faceCulling.cullFace != fc.cullFace )
		{
			glCullFace( fc.cullFace );
			_renderState.faceCulling.cullFace = fc.cullFace;
		}

		if( _renderState.faceCulling.frontFaceWindingOrder != fc.frontFaceWindingOrder )
		{
			glFrontFace( fc.frontFaceWindingOrder );
			_renderState.faceCulling.frontFaceWindingOrder = fc.frontFaceWindingOrder;
		}
	}

}

// ------------------------------------------------------------------------------------------------
void Renderer::applyProgramPointSize( const ProgramPointSize &programPointSize )
{
	if( _renderState.programPointSize.enabled != programPointSize.enabled )
	{
		enable( GL_POINT_SIZE, programPointSize.enabled );
		_renderState.programPointSize = programPointSize;
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyRasterizationMode( const RenderState::RasterizationMode &rasterizationMode )
{
	if( _renderState.rasterizationMode != rasterizationMode )
	{
		glPolygonMode( FrontAndBack, rasterizationMode );
		_renderState.rasterizationMode = rasterizationMode;
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyScissorTest( const ScissorTest &scissorTest )
{
	Rectangle rectangle = scissorTest.rect;

	if( rectangle.width < 0)
	{
#if 0
		throw new ArgumentOutOfRangeException(
			"renderState.ScissorTest.Rectangle.Width must be greater than or equal to zero.", 
			"renderState");
#endif
	}

	if( rectangle.height < 0)
	{
#if 0
		throw new ArgumentOutOfRangeException(
			"renderState.ScissorTest.Rectangle.Height must be greater than or equal to zero.",
			"renderState");
#endif
	}

	if( _renderState.scissorTest.enabled != scissorTest.enabled )
	{
		enable( GL_SCISSOR_TEST, scissorTest.enabled );
		_renderState.scissorTest.enabled = scissorTest.enabled;
	}

	if( scissorTest.enabled )
	{
		if( !_renderState.scissorTest.rect.equals(scissorTest.rect) )
		{
			glScissor( rectangle.left, rectangle.bottom, rectangle.width, rectangle.height );
			_renderState.scissorTest.rect = scissorTest.rect;
		}
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyStencilTest( const StencilTest &stencilTest )
{
	if( _renderState.stencilTest.enabled != stencilTest.enabled )
	{
		enable( GL_STENCIL_TEST, stencilTest.enabled );
		_renderState.stencilTest.enabled = stencilTest.enabled;
	}

	if( stencilTest.enabled )
	{
		applyStencil( Front, _renderState.stencilTest.frontFace, stencilTest.frontFace );
		applyStencil( Back,  _renderState.stencilTest.backFace,  stencilTest.backFace  );
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyStencil( const Face &face, StencilTestFace &currentTest, const StencilTestFace &test)
{
	if ((currentTest.stencilFailOperation          != test.stencilFailOperation) ||
		(currentTest.depthFailStencilPassOperation != test.depthFailStencilPassOperation) ||
		(currentTest.depthPassStencilPassOperation != test.depthPassStencilPassOperation))
	{
		glStencilOpSeparate(face,
			test.stencilFailOperation,
			test.depthFailStencilPassOperation,
			test.depthPassStencilPassOperation);

		currentTest.stencilFailOperation          = test.stencilFailOperation;
		currentTest.depthFailStencilPassOperation = test.depthFailStencilPassOperation;
		currentTest.depthPassStencilPassOperation = test.depthPassStencilPassOperation;
	}

	if ((currentTest.function       != test.function) ||
		(currentTest.referenceValue != test.referenceValue) ||
		(currentTest.mask           != test.mask))
	{
		glStencilFuncSeparate(face,
			test.function,
			test.referenceValue,
			test.mask);

		currentTest.function       = test.function;
		currentTest.referenceValue = test.referenceValue;
		currentTest.mask           = test.mask;
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyDepthTest( const DepthTest &depthTest)
{
	if( _renderState.depthTest.enabled != depthTest.enabled )
	{
		enable( GL_DEPTH_TEST, depthTest.enabled );
		_renderState.depthTest.enabled = depthTest.enabled;
	}

	if( depthTest.enabled )
	{
		if( _renderState.depthTest.function != depthTest.function )
		{
			glDepthFunc( depthTest.function );
			_renderState.depthTest.function = depthTest.function;
		}
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyDepthRange( const DepthRange &depthRange)
{
#if 0
	if( depthRange.near() < 0.0 || depthRange.near() > 1.0)
	{
		throw new ArgumentOutOfRangeException(
			"renderState.DepthRange.Near must be between zero and one.",
			"depthRange");
	}

	if (depthRange.far() < 0.0 || depthRange.far() > 1.0)
	{
		throw new ArgumentOutOfRangeException(
			"renderState.DepthRange.Far must be between zero and one.",
			"depthRange");
	}
#endif

	if( (_renderState.depthRange.nearValue != depthRange.nearValue) ||
		(_renderState.depthRange.farValue  != depthRange.farValue) )
	{
		glDepthRange( depthRange.nearValue, depthRange.farValue );
		_renderState.depthRange.nearValue = depthRange.nearValue;
		_renderState.depthRange.farValue  = depthRange.farValue;
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyBlending( const Blending &blending )
{
	if( _renderState.blending.enabled != blending.enabled )
	{
		enable( GL_BLEND, blending.enabled );
		_renderState.blending.enabled = blending.enabled;
	}

	if( blending.enabled )
	{
		if( (_renderState.blending.sourceRGBFactor        != blending.sourceRGBFactor)      ||
			(_renderState.blending.destinationRGBFactor   != blending.destinationRGBFactor) ||
			(_renderState.blending.sourceAlphaFactor      != blending.sourceAlphaFactor)    ||
			(_renderState.blending.destinationAlphaFactor != blending.destinationAlphaFactor))
		{
			glBlendFuncSeparate(
				blending.sourceRGBFactor,
				blending.destinationRGBFactor,
				blending.sourceAlphaFactor,
				blending.destinationAlphaFactor);

			_renderState.blending.sourceRGBFactor        = blending.sourceRGBFactor;
			_renderState.blending.destinationRGBFactor   = blending.destinationRGBFactor;
			_renderState.blending.sourceAlphaFactor      = blending.sourceAlphaFactor;
			_renderState.blending.destinationAlphaFactor = blending.destinationAlphaFactor;
		}

		if( (_renderState.blending.rgbEquation   != blending.rgbEquation)  ||
			(_renderState.blending.alphaEquation != blending.alphaEquation) )
		{
			glBlendEquationSeparate(
				blending.rgbEquation,
				blending.alphaEquation);

			_renderState.blending.rgbEquation   = blending.rgbEquation;
			_renderState.blending.alphaEquation = blending.alphaEquation;
		}

		if( !_renderState.blending.color.equals( blending.color ) )
		{
			glBlendColor( blending.color.r(),blending.color.g(),blending.color.b(),blending.color.a() );
			_renderState.blending.color = blending.color;
		}
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyColorMask( const ColorMask &colorMask )
{
	if( !_renderState.colorMask.equals(colorMask) )
	{
		glColorMask( colorMask.r, colorMask.g, colorMask.b, colorMask.a );
		_renderState.colorMask = colorMask;
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::applyDepthMask(bool depthMask)
{
	if( _renderState.depthMask != depthMask )
	{
		glDepthMask(depthMask);
		_renderState.depthMask = depthMask;
	}
}

// ------------------------------------------------------------------------------------------------
//void Renderer::applyShaderProgram( const DrawState &drawState )
//{
//	ShaderProgramGL3x shaderProgramGL3x = (ShaderProgramGL3x)drawState.ShaderProgram;
//
//	if (_boundShaderProgram != shaderProgramGL3x)
//	{
//		shaderProgramGL3x.Bind();
//		_boundShaderProgram = shaderProgramGL3x;
//	}
//	_boundShaderProgram.Clean(this, drawState, sceneState);
//}

// ------------------------------------------------------------------------------------------------
//void Renderer::applyFramebuffer()
//{
//	if (_boundFramebuffer != _setFramebuffer)
//	{
//		if (_setFramebuffer != null)
//		{
//			_setFramebuffer.Bind();
//		}
//		else
//		{
//			FramebufferGL3x.UnBind();
//		}
//
//		_boundFramebuffer = _setFramebuffer;
//	}
//
//	if (_setFramebuffer != null)
//		_setFramebuffer.Clean();
//}

// ------------------------------------------------------------------------------------------------
void Renderer::setViewport( const Rectangle &r )
{
	if( r.width < 0 || r.height < 0 )
	{
#if 0
		throw new ArgumentOutOfRangeException("Viewport", "The viewport width and height must be greater than or equal to zero.");
#endif
	}

	if( !_viewport.equals(r) )
	{
		_viewport = r;
		glViewport( r.left, r.bottom, r.width, r.height );
	}
}

// ------------------------------------------------------------------------------------------------
Rectangle Renderer::viewport() const { return _viewport; }

// ------------------------------------------------------------------------------------------------
void Renderer::clear( const ClearState &cs )
{
	//applyFramebuffer();

	applyScissorTest( cs.scissorTest );
	applyColorMask(   cs.colorMask    );
	applyDepthMask(   cs.depthMask    );
	// TODO: StencilMaskSeparate

	if( !_clearColor.equals( cs.color ) )
	{
		glClearColor( cs.color.r(), cs.color.g(), cs.color.b(), cs.color.a() );
		_clearColor = cs.color ;
	}

	if( _clearDepth != cs.depth )
	{
		glClearDepth((double)cs.depth );
		_clearDepth = cs.depth;
	}

	if( _clearStencil != cs.stencil )
	{
		glClearStencil( cs.stencil );
		_clearStencil = cs.stencil;
	}

	glClear( cs.buffers );
}

// ------------------------------------------------------------------------------------------------
void Renderer::draw( int PRIMITIVE_TYPE, const RenderState &rs )
{
	//VerifyDraw(drawState, sceneState);
	//ApplyBeforeDraw(drawState, sceneState);

	//VertexArrayGL3x vertexArray = (VertexArrayGL3x)drawState.VertexArray;
	//IndexBufferGL3x indexBuffer = vertexArray.IndexBuffer as IndexBufferGL3x;

	//if( indexBuffer != null )
	//{
	//	GL.DrawRangeElements(TypeConverterGL3x.To(primitiveType),
	//		0, vertexArray.MaximumArrayIndex(), indexBuffer.Count,
	//		TypeConverterGL3x.To(indexBuffer.Datatype), new IntPtr());
	//}
	//else
	//{
	//	GL.DrawArrays(TypeConverterGL3x.To(primitiveType), 0,
	//		vertexArray.MaximumArrayIndex() + 1);
	//}
}